Introduction
library(tidyverse) # for data cleaning and plotting
library(gplots) # for col2hex() function
library(RColorBrewer) # for color palettes
library(sf) # for working with spatial data
library(leaflet) # for highly customizable mapping
library(carData) # for Minneapolis police stops data
library(tidytuesdayR) # for bigfoot data
library(ggthemes) # for more themes (including theme_map())
library(htmltools)
theme_set(theme_minimal())
Features
Interactive panning/zooming Compose maps using arbitrary
combinations of: ·Map tiles ·Markers ·Polygons ·Lines ·Popups ·GeoJSON
Create maps right from the R console or RStudio Embed maps in
knitr/R Markdown documents and Shiny apps Easily render spatial
objects from the sp or sf packages, or data frames with
latitude/longitude columns Use map bounds and mouse events to drive
Shiny logic Display maps in non spherical mercator projections
*Augment map features using chosen plugins from leaflet plugins
repository
Basemaps
We are gonna start from the bottokm layer up. The first thing to look
it is the basemaps. This means, what will be behind your points or
shapes. There are many options and they are super easy to load in. We
can start off with the basic basic map, simpple blue ocean and white
land, if you zoom in there will be more detials
leaflet() %>%
addTiles()
We can also start the map zoomed in on a specific area using latitude
and longitude and the setView() function
Here is Saint Paul MN, now look up your hometown and practice puting
in the coordinates.
leaflet() %>%
addTiles() %>%
setView(lng = -93.093124, lat = 44.949642, zoom = 12)
hometown <- leaflet() %>%
setView(lng = -93.093124, lat = 44.949642, zoom = 12) %>%
addTiles()
There are many other basemaps you can use in leaflet! To get
different basemaps use the function addProviderTiles(), you will need to
know the name of the basemap Here are some examples:
hometown %>%
addProviderTiles(providers$CartoDB.Positron)
hometown %>%
addProviderTiles(providers$Stamen.Watercolor)
hometown %>%
addProviderTiles(providers$CartoDB.DarkMatterNoLabels)
To get the full list of basemaps available through this function
click here: http://leaflet-extras.github.io/leaflet-providers/preview/index.html
Let say you really like the water color basemap but there arent any
labels, you can layer base maps. Here I have layed the water color with
the light grey basemap that had labels. As long as you set the one on
top to have a lower opacity you can see both!
hometown %>% addProviderTiles(providers$Stamen.Watercolor) %>%
addProviderTiles(providers$CartoDB.Positron,
options = providerTileOptions(opacity = 0.5))
Markers
Now we have our basemap figured out whe can add markers. To do that
we will needed data and the observation should have a latitude and
longitdue variable to put them on the map.
tuesdata <- tidytuesdayR::tt_load('2022-09-13') #Loading in data from a tidy tuesday that has geographical points
##
## Downloading file 1 of 1: `bigfoot.csv`
bigfoot <- tuesdata$bigfoot
leaflet(data = bigfoot) %>% addTiles() %>%
addMarkers(~longitude, ~latitude)
If R is running slowly now its probably becuase there are so many
data points. It is best to filter out observations that you need before
making a map.
bigfootsub <- bigfoot %>%
filter(season == "Summer")
leaflet(data = bigfootsub) %>% addTiles() %>%
addMarkers(~longitude, ~latitude)
With less points the software runs far smoother. Now we can add some
fun things!
Plain Markers and Popups
Not only can you add labels to these points but with leaflet you can
add popups
leaflet(data = bigfootsub) %>%
addProviderTiles(providers$CartoDB.Positron) %>% #Changing basemap to something more neutral
addMarkers(~longitude, ~latitude,label = ~date, popup = ~location_details) # label means what will appear when you hover over the point and popup means what will appear when you click on a point
Awesome Markers
Awesome markers allow you to change the color of the marker dependent
on a variable
# After choosing temperature_high to be my variable I am visualizing I am going to filter out any observations that do not have a value for temperature_high
bftemp <- bigfootsub %>%
filter(temperature_high != "NA")
bftemp
#Then we will need to assign values of temperature_high to colors creating a getColor function
getColor <- function(bftemp) {
sapply(bftemp$temperature_high, function(temperature_high){
if(temperature_high <= 68) {
"blue"
} else if(temperature_high >= 69) {
"red"
} else {
"green"
} })
}
icons <- awesomeIcons(
icon = 'ios-close',
iconColor = 'pink', #Controls color of middle x
library = 'ion',
markerColor = getColor(bftemp) #Calls the function
)
leaflet(bftemp) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addAwesomeMarkers(~longitude, ~latitude,label = ~date, popup = ~location_details, icon = icons) #make sure to set the icons
Color
# load continuous dataset
data_site <-
"https://www.macalester.edu/~dshuman1/data/112/2014-Q4-Trips-History-Data.rds"
Trips <- readRDS(gzcon(url(data_site)))
Stations<-read_csv("http://www.macalester.edu/~dshuman1/data/112/DC-Stations.csv")
departSta <- Trips %>%
left_join(Stations, by = c("sstation" = "name")) %>%
group_by(lat, long) %>%
summarise(EventsCount = n())
Create Color Palette
# Call the color function (colorNumeric) to create a new palette function
pal <- colorNumeric(c("red", "green", "blue"), 1:10)
# Pass the palette function a data vector to get the corresponding colors
pal(c(1,6,9))
## [1] "#FF0000" "#52E74B" "#6854D8"
# create another color palette function with the range of inputs (i.e. domain)
palDomain <- colorNumeric(
palette = "Blues",
domain = departSta$EventsCount)
# Show the corresponding colors
head(palDomain(departSta$EventsCount))
## [1] "#F5FAFE" "#EFF6FC" "#F2F8FD" "#F5F9FE" "#EEF5FC" "#EFF6FC"
Common parameters
#RColorBrewer
palBre <- colorNumeric(
palette = "RdYlBu",
domain = departSta$EventsCount)
#viridis
palVir <- colorNumeric(
palette = "magma",
domain = departSta$EventsCount)
#RGB or Named the colors: palette(), c("#000000", "#0000FF", "#FFFFFF"), topo.colors(10) etc
#A function that receives a single value between 0 and 1 and returns a color: colorRamp(c("#000000", "#FFFFFF"), interpolate="spline") etc
Continuous data
#Continuous input, continuous colors (colorNumeric)
palConC <- colorNumeric(
palette = "RdYlBu",
domain = departSta$EventsCount)
#Continuous input, discrete colors (colorBin and colorQuantile)
# colorBin:slicing the input domain up by value(bin)
palBin<-colorBin("Blues", departSta$EventsCount, 5, pretty = FALSE)
#colorQuantile: slicing the input domain into subsets with equal numbers of observations (by quantile)
palQuan <- colorQuantile("Blues", departSta$EventsCount, n = 7)
# leaflet(data = departSta) %>%
# addProviderTiles(providers$CartoDB.DarkMatter) %>%
# addProviderTiles(providers$Stamen.TonerLines,
# options = providerTileOptions(opacity = 0.35)) %>%
# addProviderTiles(providers$Stamen.TonerLabels) %>%
# addCircles(lng = ~long,
# lat = ~lat,
# #stroke width in pixels
# weight = 10,
# #changes transparency, like alpha in ggplot
# opacity = 1,
# color = ~palQuan(EventsCount))
categorical data
#Domain
palFacD<-colorFactor(palette = "Blues", MplsStops$problem)
#Level
palFacL<-colorFactor(topo.colors(5),levels = MplsStops$problem)
# leaflet(data = MplsStops) %>%
# addProviderTiles(providers$CartoDB.Positron) %>%
# addCircleMarkers(lng = ~long,
# lat = ~lat,
# weight = 1,
# opacity = 1,
# stroke = TRUE,
# color = ~palFacL(problem))
Lines and Shape
#rectangle
leaflet(data = departSta) %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addProviderTiles(providers$Stamen.TonerLines,
options = providerTileOptions(opacity = 0.35)) %>%
addProviderTiles(providers$Stamen.TonerLabels) %>%
addCircles(
lng = ~ long,
lat = ~ lat,
#stroke width in pixels
weight = 10,
#changes transparency, like alpha in ggplot
opacity = 1,
color = ~ palQuan(EventsCount)
) %>%
addRectangles(
lng1 = -77.20250,
lat1 =38.80111,
lng2 =-76.93186,
lat2 = 39.12351,
fillColor = "transparent"
)
#Polygons and Polylines
leaflet(data = departSta) %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addProviderTiles(providers$Stamen.TonerLines,
options = providerTileOptions(opacity = 0.35)) %>%
addProviderTiles(providers$Stamen.TonerLabels) %>%
addPolygons(
lng = ~ long,
lat = ~ lat,
# set the opacity of the outline
opacity = 1,
# set the stroke width in pixels
weight = 1,
# set the fill opacity
fillOpacity = 0.6
)
Legend
leaflet(data = MplsStops) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(lng = ~long,
lat = ~lat,
weight = 1,
opacity = 1,
stroke = TRUE,
color = ~palFacL(problem)) %>%
addLegend(position = "bottomleft",
pal = palFacL,
values = ~problem,
title = "Type of Stops")
Choropleth Map
states <- geojsonio::geojson_read("https://rstudio.github.io/leaflet/json/us-states.geojson", what = "sp")
bins <- c(0, 10, 20, 50, 100, 200, 500, 1000, Inf)
pal <- colorBin("YlOrRd", domain = states$density, bins = bins)
labels <- sprintf(
"<strong>%s</strong><br/>%g people / mi<sup>2</sup>",
states$name, states$density
) %>% lapply(htmltools::HTML)
leaflet(states) %>%
setView(-96, 37.8, 4) %>%
addProviderTiles("MapBox", options = providerTileOptions(
id = "mapbox.light",
accessToken = Sys.getenv('MAPBOX_ACCESS_TOKEN'))) %>%
addPolygons(
fillColor = ~pal(density),
weight = 2,
opacity = 1,
color = "white",
dashArray = "3",
fillOpacity = 0.7,
# hightlight the polygon when curse over it
highlightOptions = highlightOptions(
weight = 5,
color = "#666",
dashArray = "",
fillOpacity = 0.7,
bringToFront = TRUE),
label = labels,
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "15px",
direction = "auto"))
LS0tCnRpdGxlOiAnTGVhcm5pbmcgR3VpZGUgRHJhZnQnCmF1dGhvcjogIlBpcHBhLFJpdGEsSmVubnkiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIGtlZXBfbWQ6IFRSVUUKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICBkZl9wcmludDogcGFnZWQKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBlcnJvcj1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQpgYGAKCiMgSW50cm9kdWN0aW9uCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpICAgICAjIGZvciBkYXRhIGNsZWFuaW5nIGFuZCBwbG90dGluZwpsaWJyYXJ5KGdwbG90cykgICAgICAgICMgZm9yIGNvbDJoZXgoKSBmdW5jdGlvbgpsaWJyYXJ5KFJDb2xvckJyZXdlcikgICMgZm9yIGNvbG9yIHBhbGV0dGVzCmxpYnJhcnkoc2YpICAgICAgICAgICAgIyBmb3Igd29ya2luZyB3aXRoIHNwYXRpYWwgZGF0YQpsaWJyYXJ5KGxlYWZsZXQpICAgICAgICMgZm9yIGhpZ2hseSBjdXN0b21pemFibGUgbWFwcGluZwpsaWJyYXJ5KGNhckRhdGEpICAgICAgICMgZm9yIE1pbm5lYXBvbGlzIHBvbGljZSBzdG9wcyBkYXRhCmxpYnJhcnkodGlkeXR1ZXNkYXlSKSAgIyBmb3IgYmlnZm9vdCBkYXRhIApsaWJyYXJ5KGdndGhlbWVzKSAgICAgICMgZm9yIG1vcmUgdGhlbWVzIChpbmNsdWRpbmcgdGhlbWVfbWFwKCkpCmxpYnJhcnkoaHRtbHRvb2xzKQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQpgYGAKCiMjIEZlYXR1cmVzCkludGVyYWN0aXZlIHBhbm5pbmcvem9vbWluZwogICpDb21wb3NlIG1hcHMgdXNpbmcgYXJiaXRyYXJ5IGNvbWJpbmF0aW9ucyBvZjoKICAgICAgwrdNYXAgdGlsZXMKICAgICAgwrdNYXJrZXJzCiAgICAgIMK3UG9seWdvbnMKICAgICAgwrdMaW5lcwogICAgICDCt1BvcHVwcwogICAgICDCt0dlb0pTT04KICAqQ3JlYXRlIG1hcHMgcmlnaHQgZnJvbSB0aGUgUiBjb25zb2xlIG9yIFJTdHVkaW8KICAqRW1iZWQgbWFwcyBpbiBrbml0ci9SIE1hcmtkb3duIGRvY3VtZW50cyBhbmQgU2hpbnkgYXBwcwogICpFYXNpbHkgcmVuZGVyIHNwYXRpYWwgb2JqZWN0cyBmcm9tIHRoZSBzcCBvciBzZiBwYWNrYWdlcywgb3IgZGF0YSBmcmFtZXMgd2l0aCBsYXRpdHVkZS9sb25naXR1ZGUgY29sdW1ucwogICpVc2UgbWFwIGJvdW5kcyBhbmQgbW91c2UgZXZlbnRzIHRvIGRyaXZlIFNoaW55IGxvZ2ljCiAgKkRpc3BsYXkgbWFwcyBpbiBub24gc3BoZXJpY2FsIG1lcmNhdG9yIHByb2plY3Rpb25zCiAgKkF1Z21lbnQgbWFwIGZlYXR1cmVzIHVzaW5nIGNob3NlbiBwbHVnaW5zIGZyb20gbGVhZmxldCBwbHVnaW5zIHJlcG9zaXRvcnkKCgojIEJhc2VtYXBzCgpXZSBhcmUgZ29ubmEgc3RhcnQgZnJvbSB0aGUgYm90dG9rbSBsYXllciB1cC4gVGhlIGZpcnN0IHRoaW5nIHRvIGxvb2sgaXQgaXMgdGhlIGJhc2VtYXBzLiBUaGlzIG1lYW5zLCB3aGF0IHdpbGwgYmUgYmVoaW5kIHlvdXIgcG9pbnRzIG9yIHNoYXBlcy4gVGhlcmUgYXJlIG1hbnkgb3B0aW9ucyBhbmQgdGhleSBhcmUgc3VwZXIgZWFzeSB0byBsb2FkIGluLiAKV2UgY2FuIHN0YXJ0IG9mZiB3aXRoIHRoZSBiYXNpYyBiYXNpYyBtYXAsIHNpbXBwbGUgYmx1ZSBvY2VhbiBhbmQgd2hpdGUgbGFuZCwgaWYgeW91IHpvb20gaW4gdGhlcmUgd2lsbCBiZSBtb3JlIGRldGlhbHMKCmBgYHtyfQpsZWFmbGV0KCkgJT4lIAogIGFkZFRpbGVzKCkKYGBgCgpXZSBjYW4gYWxzbyBzdGFydCB0aGUgbWFwIHpvb21lZCBpbiBvbiBhIHNwZWNpZmljIGFyZWEgdXNpbmcgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBhbmQgdGhlIHNldFZpZXcoKSBmdW5jdGlvbiAKCkhlcmUgaXMgU2FpbnQgUGF1bCBNTiwgbm93IGxvb2sgdXAgeW91ciBob21ldG93biBhbmQgcHJhY3RpY2UgcHV0aW5nIGluIHRoZSBjb29yZGluYXRlcy4gCgpgYGB7cn0KbGVhZmxldCgpICU+JSAKICBhZGRUaWxlcygpICU+JSAKICBzZXRWaWV3KGxuZyA9IC05My4wOTMxMjQsIGxhdCA9IDQ0Ljk0OTY0Miwgem9vbSA9IDEyKQogIApgYGAKCmBgYHtyfQpob21ldG93biA8LSBsZWFmbGV0KCkgJT4lIAogIHNldFZpZXcobG5nID0gLTkzLjA5MzEyNCwgbGF0ID0gNDQuOTQ5NjQyLCB6b29tID0gMTIpICU+JSAKICAgIGFkZFRpbGVzKCkKICAKYGBgCgpUaGVyZSBhcmUgbWFueSBvdGhlciBiYXNlbWFwcyB5b3UgY2FuIHVzZSBpbiBsZWFmbGV0ISAKVG8gZ2V0IGRpZmZlcmVudCBiYXNlbWFwcyB1c2UgdGhlIGZ1bmN0aW9uIGFkZFByb3ZpZGVyVGlsZXMoKSwgeW91IHdpbGwgbmVlZCB0byBrbm93IHRoZSBuYW1lIG9mIHRoZSBiYXNlbWFwIApIZXJlIGFyZSBzb21lIGV4YW1wbGVzOiAKCmBgYHtyfQpob21ldG93biAlPiUgCiAgICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKQpgYGAKCmBgYHtyfQpob21ldG93biAlPiUgCiAgICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uV2F0ZXJjb2xvcikKYGBgCgpgYGB7cn0KaG9tZXRvd24gJT4lIAogICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyTm9MYWJlbHMpCmBgYAoKVG8gZ2V0IHRoZSBmdWxsIGxpc3Qgb2YgYmFzZW1hcHMgYXZhaWxhYmxlIHRocm91Z2ggdGhpcyBmdW5jdGlvbiBjbGljayBoZXJlOiBodHRwOi8vbGVhZmxldC1leHRyYXMuZ2l0aHViLmlvL2xlYWZsZXQtcHJvdmlkZXJzL3ByZXZpZXcvaW5kZXguaHRtbCAKCkxldCBzYXkgeW91IHJlYWxseSBsaWtlIHRoZSB3YXRlciBjb2xvciBiYXNlbWFwIGJ1dCB0aGVyZSBhcmVudCBhbnkgbGFiZWxzLCB5b3UgY2FuIGxheWVyIGJhc2UgbWFwcy4gSGVyZSBJIGhhdmUgbGF5ZWQgdGhlIHdhdGVyIGNvbG9yIHdpdGggdGhlIGxpZ2h0IGdyZXkgYmFzZW1hcCB0aGF0IGhhZCBsYWJlbHMuIEFzIGxvbmcgYXMgeW91IHNldCB0aGUgb25lIG9uIHRvcCB0byBoYXZlIGEgbG93ZXIgb3BhY2l0eSB5b3UgY2FuIHNlZSBib3RoISAgCgpgYGB7cn0KaG9tZXRvd24gJT4lIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5XYXRlcmNvbG9yKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uLAogICAgb3B0aW9ucyA9IHByb3ZpZGVyVGlsZU9wdGlvbnMob3BhY2l0eSA9IDAuNSkpIAogCmBgYAoKIyBNYXJrZXJzIAoKTm93IHdlIGhhdmUgb3VyIGJhc2VtYXAgZmlndXJlZCBvdXQgd2hlIGNhbiBhZGQgbWFya2Vycy4gVG8gZG8gdGhhdCB3ZSB3aWxsIG5lZWRlZCBkYXRhIGFuZCB0aGUgb2JzZXJ2YXRpb24gc2hvdWxkIGhhdmUgYSBsYXRpdHVkZSBhbmQgbG9uZ2l0ZHVlIHZhcmlhYmxlIHRvIHB1dCB0aGVtIG9uIHRoZSBtYXAuIAoKYGBge3J9CnR1ZXNkYXRhIDwtIHRpZHl0dWVzZGF5Ujo6dHRfbG9hZCgnMjAyMi0wOS0xMycpICNMb2FkaW5nIGluIGRhdGEgZnJvbSBhIHRpZHkgdHVlc2RheSB0aGF0IGhhcyBnZW9ncmFwaGljYWwgcG9pbnRzCmJpZ2Zvb3QgPC0gdHVlc2RhdGEkYmlnZm9vdApgYGAKCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBiaWdmb290KSAlPiUgYWRkVGlsZXMoKSAlPiUKICBhZGRNYXJrZXJzKH5sb25naXR1ZGUsIH5sYXRpdHVkZSkKYGBgCgpJZiBSIGlzIHJ1bm5pbmcgc2xvd2x5IG5vdyBpdHMgcHJvYmFibHkgYmVjdWFzZSB0aGVyZSBhcmUgc28gbWFueSBkYXRhIHBvaW50cy4gSXQgaXMgYmVzdCB0byBmaWx0ZXIgb3V0IG9ic2VydmF0aW9ucyB0aGF0IHlvdSBuZWVkIGJlZm9yZSBtYWtpbmcgYSBtYXAuIAoKYGBge3J9CmJpZ2Zvb3RzdWIgPC0gYmlnZm9vdCAlPiUgCiAgZmlsdGVyKHNlYXNvbiA9PSAiU3VtbWVyIikKYGBgCgpgYGB7cn0KbGVhZmxldChkYXRhID0gYmlnZm9vdHN1YikgJT4lIGFkZFRpbGVzKCkgJT4lCiAgYWRkTWFya2Vycyh+bG9uZ2l0dWRlLCB+bGF0aXR1ZGUpCmBgYApXaXRoIGxlc3MgcG9pbnRzIHRoZSBzb2Z0d2FyZSBydW5zIGZhciBzbW9vdGhlci4gTm93IHdlIGNhbiBhZGQgc29tZSBmdW4gdGhpbmdzISAKCiMgUGxhaW4gTWFya2VycyBhbmQgUG9wdXBzCk5vdCBvbmx5IGNhbiB5b3UgYWRkIGxhYmVscyB0byB0aGVzZSBwb2ludHMgYnV0IHdpdGggbGVhZmxldCB5b3UgY2FuIGFkZCBwb3B1cHMgCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBiaWdmb290c3ViKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lICNDaGFuZ2luZyBiYXNlbWFwIHRvIHNvbWV0aGluZyBtb3JlIG5ldXRyYWwgCiAgYWRkTWFya2Vycyh+bG9uZ2l0dWRlLCB+bGF0aXR1ZGUsbGFiZWwgPSB+ZGF0ZSwgcG9wdXAgPSB+bG9jYXRpb25fZGV0YWlscykgIyBsYWJlbCBtZWFucyB3aGF0IHdpbGwgYXBwZWFyIHdoZW4geW91IGhvdmVyIG92ZXIgdGhlIHBvaW50IGFuZCBwb3B1cCBtZWFucyB3aGF0IHdpbGwgYXBwZWFyIHdoZW4geW91IGNsaWNrIG9uIGEgcG9pbnQKYGBgCgojIEF3ZXNvbWUgTWFya2VycwpBd2Vzb21lIG1hcmtlcnMgYWxsb3cgeW91IHRvIGNoYW5nZSB0aGUgY29sb3Igb2YgdGhlIG1hcmtlciBkZXBlbmRlbnQgb24gYSB2YXJpYWJsZSAKYGBge3J9CiMgQWZ0ZXIgY2hvb3NpbmcgdGVtcGVyYXR1cmVfaGlnaCB0byBiZSBteSB2YXJpYWJsZSBJIGFtIHZpc3VhbGl6aW5nIEkgYW0gZ29pbmcgdG8gZmlsdGVyIG91dCBhbnkgb2JzZXJ2YXRpb25zIHRoYXQgZG8gbm90IGhhdmUgYSB2YWx1ZSBmb3IgdGVtcGVyYXR1cmVfaGlnaApiZnRlbXAgPC0gYmlnZm9vdHN1YiAlPiUgCiAgZmlsdGVyKHRlbXBlcmF0dXJlX2hpZ2ggIT0gIk5BIikKYmZ0ZW1wCmBgYAoKCmBgYHtyfQoKI1RoZW4gd2Ugd2lsbCBuZWVkIHRvIGFzc2lnbiB2YWx1ZXMgb2YgdGVtcGVyYXR1cmVfaGlnaCAgdG8gY29sb3JzIGNyZWF0aW5nIGEgZ2V0Q29sb3IgZnVuY3Rpb24gCgpnZXRDb2xvciA8LSBmdW5jdGlvbihiZnRlbXApIHsKICBzYXBwbHkoYmZ0ZW1wJHRlbXBlcmF0dXJlX2hpZ2gsIGZ1bmN0aW9uKHRlbXBlcmF0dXJlX2hpZ2gpewogIGlmKHRlbXBlcmF0dXJlX2hpZ2ggPD0gNjgpIHsgCiAgICAiYmx1ZSIKICB9IGVsc2UgaWYodGVtcGVyYXR1cmVfaGlnaCA+PSA2OSkgewogICAgInJlZCIKICB9IGVsc2UgewogICAgImdyZWVuIgogIH0gfSkKfQoKaWNvbnMgPC0gYXdlc29tZUljb25zKAogIGljb24gPSAnaW9zLWNsb3NlJywKICBpY29uQ29sb3IgPSAncGluaycsICNDb250cm9scyBjb2xvciBvZiBtaWRkbGUgeCAKICBsaWJyYXJ5ID0gJ2lvbicsCiAgbWFya2VyQ29sb3IgPSBnZXRDb2xvcihiZnRlbXApICNDYWxscyB0aGUgZnVuY3Rpb24gCikKCmxlYWZsZXQoYmZ0ZW1wKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lIAogIGFkZEF3ZXNvbWVNYXJrZXJzKH5sb25naXR1ZGUsIH5sYXRpdHVkZSxsYWJlbCA9IH5kYXRlLCBwb3B1cCA9IH5sb2NhdGlvbl9kZXRhaWxzLCBpY29uID0gaWNvbnMpICNtYWtlIHN1cmUgdG8gc2V0IHRoZSBpY29ucwpgYGAKICAKIyBDb2xvcgoKYGBge3J9CiMgbG9hZCBjb250aW51b3VzIGRhdGFzZXQKZGF0YV9zaXRlIDwtIAogICJodHRwczovL3d3dy5tYWNhbGVzdGVyLmVkdS9+ZHNodW1hbjEvZGF0YS8xMTIvMjAxNC1RNC1Ucmlwcy1IaXN0b3J5LURhdGEucmRzIiAKVHJpcHMgPC0gcmVhZFJEUyhnemNvbih1cmwoZGF0YV9zaXRlKSkpClN0YXRpb25zPC1yZWFkX2NzdigiaHR0cDovL3d3dy5tYWNhbGVzdGVyLmVkdS9+ZHNodW1hbjEvZGF0YS8xMTIvREMtU3RhdGlvbnMuY3N2IikKCmRlcGFydFN0YSA8LSBUcmlwcyAlPiUKICBsZWZ0X2pvaW4oU3RhdGlvbnMsIGJ5ID0gYygic3N0YXRpb24iID0gIm5hbWUiKSkgJT4lCiAgZ3JvdXBfYnkobGF0LCBsb25nKSAlPiUKICBzdW1tYXJpc2UoRXZlbnRzQ291bnQgPSBuKCkpCmBgYAoKIyMgQ3JlYXRlIENvbG9yIFBhbGV0dGUKYGBge3J9CiMgQ2FsbCB0aGUgY29sb3IgZnVuY3Rpb24gKGNvbG9yTnVtZXJpYykgdG8gY3JlYXRlIGEgbmV3IHBhbGV0dGUgZnVuY3Rpb24KcGFsIDwtIGNvbG9yTnVtZXJpYyhjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIpLCAxOjEwKQojIFBhc3MgdGhlIHBhbGV0dGUgZnVuY3Rpb24gYSBkYXRhIHZlY3RvciB0byBnZXQgdGhlIGNvcnJlc3BvbmRpbmcgY29sb3JzCnBhbChjKDEsNiw5KSkKIyBjcmVhdGUgYW5vdGhlciBjb2xvciBwYWxldHRlIGZ1bmN0aW9uIHdpdGggdGhlIHJhbmdlIG9mIGlucHV0cyAoaS5lLiBkb21haW4pIApwYWxEb21haW4gPC0gY29sb3JOdW1lcmljKAogIHBhbGV0dGUgPSAiQmx1ZXMiLAogIGRvbWFpbiA9IGRlcGFydFN0YSRFdmVudHNDb3VudCkKIyBTaG93IHRoZSBjb3JyZXNwb25kaW5nIGNvbG9ycwpoZWFkKHBhbERvbWFpbihkZXBhcnRTdGEkRXZlbnRzQ291bnQpKQpgYGAKCiMjIENvbW1vbiBwYXJhbWV0ZXJzCgpgYGB7cn0KI1JDb2xvckJyZXdlciAKcGFsQnJlIDwtIGNvbG9yTnVtZXJpYygKICBwYWxldHRlID0gIlJkWWxCdSIsCiAgZG9tYWluID0gZGVwYXJ0U3RhJEV2ZW50c0NvdW50KQoKI3ZpcmlkaXMKcGFsVmlyIDwtIGNvbG9yTnVtZXJpYygKICBwYWxldHRlID0gIm1hZ21hIiwKICBkb21haW4gPSBkZXBhcnRTdGEkRXZlbnRzQ291bnQpCgojUkdCIG9yIE5hbWVkIHRoZSBjb2xvcnM6IHBhbGV0dGUoKSwgYygiIzAwMDAwMCIsICIjMDAwMEZGIiwgIiNGRkZGRkYiKSwgdG9wby5jb2xvcnMoMTApIGV0YwoKI0EgZnVuY3Rpb24gdGhhdCByZWNlaXZlcyBhIHNpbmdsZSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDEgYW5kIHJldHVybnMgYSBjb2xvcjogY29sb3JSYW1wKGMoIiMwMDAwMDAiLCAiI0ZGRkZGRiIpLCBpbnRlcnBvbGF0ZT0ic3BsaW5lIikgZXRjCmBgYAoKIyMgQ29udGludW91cyBkYXRhCgpgYGB7cn0KI0NvbnRpbnVvdXMgaW5wdXQsIGNvbnRpbnVvdXMgY29sb3JzIChjb2xvck51bWVyaWMpCnBhbENvbkMgPC0gY29sb3JOdW1lcmljKAogIHBhbGV0dGUgPSAiUmRZbEJ1IiwKICBkb21haW4gPSBkZXBhcnRTdGEkRXZlbnRzQ291bnQpCgojQ29udGludW91cyBpbnB1dCwgZGlzY3JldGUgY29sb3JzIChjb2xvckJpbiBhbmQgY29sb3JRdWFudGlsZSkKCiMgY29sb3JCaW46c2xpY2luZyB0aGUgaW5wdXQgZG9tYWluIHVwIGJ5IHZhbHVlKGJpbikKcGFsQmluPC1jb2xvckJpbigiQmx1ZXMiLCBkZXBhcnRTdGEkRXZlbnRzQ291bnQsIDUsIHByZXR0eSA9IEZBTFNFKQoKI2NvbG9yUXVhbnRpbGU6IHNsaWNpbmcgdGhlIGlucHV0IGRvbWFpbiBpbnRvIHN1YnNldHMgd2l0aCBlcXVhbCBudW1iZXJzIG9mIG9ic2VydmF0aW9ucyAoYnkgcXVhbnRpbGUpCnBhbFF1YW4gPC0gY29sb3JRdWFudGlsZSgiQmx1ZXMiLCBkZXBhcnRTdGEkRXZlbnRzQ291bnQsIG4gPSA3KQpgYGAKCgpgYGB7cn0KIyBsZWFmbGV0KGRhdGEgPSBkZXBhcnRTdGEpICU+JQojICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyKSAlPiUKIyAgIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxpbmVzLAojICAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucyhvcGFjaXR5ID0gMC4zNSkpICU+JQojICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGFiZWxzKSAlPiUKIyAgICAgYWRkQ2lyY2xlcyhsbmcgPSB+bG9uZywKIyAgICAgICAgICAgICAgbGF0ID0gfmxhdCwKIyAgICAgICAgICAgICAjc3Ryb2tlIHdpZHRoIGluIHBpeGVscwojICAgICAgICAgICAgICB3ZWlnaHQgPSAxMCwKIyAgICAgICAgICAgICAjY2hhbmdlcyB0cmFuc3BhcmVuY3ksIGxpa2UgYWxwaGEgaW4gZ2dwbG90CiMgICAgICAgICAgICAgIG9wYWNpdHkgPSAxLAojICAgICAgICAgICAgICBjb2xvciA9IH5wYWxRdWFuKEV2ZW50c0NvdW50KSkKYGBgCgojIyBjYXRlZ29yaWNhbCBkYXRhCgpgYGB7cn0KI0RvbWFpbgpwYWxGYWNEPC1jb2xvckZhY3RvcihwYWxldHRlID0gIkJsdWVzIiwgTXBsc1N0b3BzJHByb2JsZW0pCiNMZXZlbApwYWxGYWNMPC1jb2xvckZhY3Rvcih0b3BvLmNvbG9ycyg1KSxsZXZlbHMgPSBNcGxzU3RvcHMkcHJvYmxlbSkKCiMgbGVhZmxldChkYXRhID0gTXBsc1N0b3BzKSAlPiUKIyAgIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQojICAgYWRkQ2lyY2xlTWFya2VycyhsbmcgPSB+bG9uZywKIyAgICAgICAgICAgICAgbGF0ID0gfmxhdCwKIyAgICAgICAgICAgICAgd2VpZ2h0ID0gMSwKIyAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsCiMgICAgICAgICAgICAgIHN0cm9rZSA9IFRSVUUsCiMgICAgICAgICAgICAgIGNvbG9yID0gfnBhbEZhY0wocHJvYmxlbSkpCmBgYAoKIyBMaW5lcyBhbmQgU2hhcGUKCmBgYHtyfQojcmVjdGFuZ2xlCmxlYWZsZXQoZGF0YSA9IGRlcGFydFN0YSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaW5lcywKICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjM1KSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGFiZWxzKSAlPiUKICBhZGRDaXJjbGVzKAogICAgbG5nID0gfiBsb25nLAogICAgbGF0ID0gfiBsYXQsCiAgICAjc3Ryb2tlIHdpZHRoIGluIHBpeGVscwogICAgd2VpZ2h0ID0gMTAsCiAgICAjY2hhbmdlcyB0cmFuc3BhcmVuY3ksIGxpa2UgYWxwaGEgaW4gZ2dwbG90CiAgICBvcGFjaXR5ID0gMSwKICAgIGNvbG9yID0gfiBwYWxRdWFuKEV2ZW50c0NvdW50KQogICkgJT4lCiAgYWRkUmVjdGFuZ2xlcygKICAgIGxuZzEgPSAtNzcuMjAyNTAsCiAgICBsYXQxID0zOC44MDExMSwKICAgIGxuZzIgPS03Ni45MzE4NiwKICAgIGxhdDIgPSAzOS4xMjM1MSwKICAgIGZpbGxDb2xvciA9ICJ0cmFuc3BhcmVudCIKICApCgojUG9seWdvbnMgYW5kIFBvbHlsaW5lcwpsZWFmbGV0KGRhdGEgPSBkZXBhcnRTdGEpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuRGFya01hdHRlcikgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGluZXMsCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucyhvcGFjaXR5ID0gMC4zNSkpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxhYmVscykgJT4lCiAgYWRkUG9seWdvbnMoCiAgICBsbmcgPSB+IGxvbmcsCiAgICBsYXQgPSB+IGxhdCwKICAgICMgc2V0IHRoZSBvcGFjaXR5IG9mIHRoZSBvdXRsaW5lCiAgICBvcGFjaXR5ID0gMSwKICAgICMgc2V0IHRoZSBzdHJva2Ugd2lkdGggaW4gcGl4ZWxzCiAgICB3ZWlnaHQgPSAxLAogICAgIyBzZXQgdGhlIGZpbGwgb3BhY2l0eQogICAgZmlsbE9wYWNpdHkgPSAwLjYKICApCmBgYAoKIyBMZWdlbmQKCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBNcGxzU3RvcHMpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogIGFkZENpcmNsZU1hcmtlcnMobG5nID0gfmxvbmcsCiAgICAgICAgICAgICBsYXQgPSB+bGF0LAogICAgICAgICAgICAgd2VpZ2h0ID0gMSwKICAgICAgICAgICAgIG9wYWNpdHkgPSAxLAogICAgICAgICAgICAgc3Ryb2tlID0gVFJVRSwKICAgICAgICAgICAgIGNvbG9yID0gfnBhbEZhY0wocHJvYmxlbSkpICU+JSAKICAgIGFkZExlZ2VuZChwb3NpdGlvbiA9ICJib3R0b21sZWZ0IiwgCiAgICAgICAgICAgIHBhbCA9IHBhbEZhY0wsCiAgICAgICAgICAgIHZhbHVlcyA9IH5wcm9ibGVtLAogICAgICAgICAgICAgdGl0bGUgPSAiVHlwZSBvZiBTdG9wcyIpIApgYGAKCgojIENob3JvcGxldGggTWFwCgpgYGB7cn0Kc3RhdGVzIDwtIGdlb2pzb25pbzo6Z2VvanNvbl9yZWFkKCJodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvanNvbi91cy1zdGF0ZXMuZ2VvanNvbiIsIHdoYXQgPSAic3AiKQoKYmlucyA8LSBjKDAsIDEwLCAyMCwgNTAsIDEwMCwgMjAwLCA1MDAsIDEwMDAsIEluZikKcGFsIDwtIGNvbG9yQmluKCJZbE9yUmQiLCBkb21haW4gPSBzdGF0ZXMkZGVuc2l0eSwgYmlucyA9IGJpbnMpCgpsYWJlbHMgPC0gc3ByaW50ZigKICAiPHN0cm9uZz4lczwvc3Ryb25nPjxici8+JWcgcGVvcGxlIC8gbWk8c3VwPjI8L3N1cD4iLAogIHN0YXRlcyRuYW1lLCBzdGF0ZXMkZGVuc2l0eQopICU+JSBsYXBwbHkoaHRtbHRvb2xzOjpIVE1MKQoKbGVhZmxldChzdGF0ZXMpICU+JQogIHNldFZpZXcoLTk2LCAzNy44LCA0KSAlPiUKICBhZGRQcm92aWRlclRpbGVzKCJNYXBCb3giLCBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucygKICAgIGlkID0gIm1hcGJveC5saWdodCIsCiAgICBhY2Nlc3NUb2tlbiA9IFN5cy5nZXRlbnYoJ01BUEJPWF9BQ0NFU1NfVE9LRU4nKSkpICU+JSAKICBhZGRQb2x5Z29ucygKICAgIGZpbGxDb2xvciA9IH5wYWwoZGVuc2l0eSksCiAgICB3ZWlnaHQgPSAyLAogICAgb3BhY2l0eSA9IDEsCiAgICBjb2xvciA9ICJ3aGl0ZSIsCiAgICBkYXNoQXJyYXkgPSAiMyIsCiAgICBmaWxsT3BhY2l0eSA9IDAuNywKICAgICMgaGlnaHRsaWdodCB0aGUgcG9seWdvbiB3aGVuIGN1cnNlIG92ZXIgaXQKICAgIGhpZ2hsaWdodE9wdGlvbnMgPSBoaWdobGlnaHRPcHRpb25zKAogICAgICB3ZWlnaHQgPSA1LAogICAgICBjb2xvciA9ICIjNjY2IiwKICAgICAgZGFzaEFycmF5ID0gIiIsCiAgICAgIGZpbGxPcGFjaXR5ID0gMC43LAogICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwKICAgIGxhYmVsID0gbGFiZWxzLAogICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKAogICAgICBzdHlsZSA9IGxpc3QoImZvbnQtd2VpZ2h0IiA9ICJub3JtYWwiLCBwYWRkaW5nID0gIjNweCA4cHgiKSwKICAgICAgdGV4dHNpemUgPSAiMTVweCIsCiAgICAgIGRpcmVjdGlvbiA9ICJhdXRvIikpCmBgYAo=